// 解构出来的都是axios里面用到的参数的数据类型(axios已写好的数据类型)
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import { useUserStore } from '@/store/modules/user'
// console.log(token)
// 导入提示框
import { message } from 'ant-design-vue'

const MSG_MAP = {
  400: '请求错误',
  401: '未授权，请登录',
  403: '拒绝访问',
  404: `请求地址出错`,
  408: '请求超时',
  500: '服务器内部错误',
  501: '服务未实现',
  502: '网关错误',
  503: '服务不可用',
  504: '网关超时',
  505: 'HTTP版本不受支持'
}

// 用class组件封装axios(面向对象编程思想编程,封装一个自定义的对象原型链)
// class就是对象的构造函数,把要写的属性和方法封装在class里面,
// 然后通过class类型实例一个对象,然后调用该对象的方法即可
// 在这里就是先把axios的配置和方法写好
// 然后直接实例axios,传入对应的配置即可
// 然后调用封装好的axios.get方法等
class Request {
  _axios: AxiosInstance = {} as AxiosInstance // 先声明axios对象
  constructor(config: AxiosRequestConfig) {
    // this指的是class类,不同与react,构造器不可以声明成员,只可以修改已有成员
    this._axios = axios.create(config) // 实例axios对象
    // 声明axios的请求拦截
    this._axios.interceptors.request.use(
      (config) => {
        // 在外面pinia还没实例会报错,但是请求拦截里面pinia已经实例不会报错
        const { token } = useUserStore()
        // 请求成功的拦截
        if (token) {
          // console.log(token, '请求头统一添加token')
          // 这种方法是统一所有请求都添加请求头
          config.headers!.Authorization = 'Bearer ' + token
        }
        return config
      },
      (error: any) => {
        // 请求失败的拦截
        return Promise.reject(error)
      }
    )
    // 声明axios的响应拦截
    this._axios.interceptors.response.use(
      (res: AxiosResponse) => {
        // 响应成功的拦截
        if (res.data.success) {
          // 如果请求成功+res.date.success都是成功,那么就会正常返回响应数据(这里是为了减少个data)
          return res.data
        } else {
          message.error(res.data.message)
          // 这里的错误拦截还要查询下,因为没有error
          // return Promise.reject(error)
          return Promise.reject(Error)
        }
      },
      (error: any) => {
        console.log('error', error)
        // 响应失败会走这条路
        // 响应失败的拦截
        // 响应错误并且报401,说明token过期,先清除token再跳回登录页
        if (error.response && error.response.status === 401) {
          const { loginOut } = useUserStore()
          // 要先清除token和userInfo再跳回登录页
          // 提醒用户
          message.error(error.response.data.message)
          loginOut()
        } else if (error.response && error.response.status) {
          message.error(MSG_MAP[error.response.status])
        }
        // 对响应错误做点什么
        return Promise.reject(error)
      }
    )
  }

  // axios.request方法是axios的基础用法,里面再写method确定请求方式
  // 这里是把axios方法再次封装一遍,定义好.then的res和catch的err的数据类型
  request<T>(config: AxiosRequestConfig): Promise<T> {
    // 再次封装request方法
    // promise对象:
    // promise就是一个对象,但是会自动执行里面相应的方法,
    // promise对象里面的代码会立马执行,内部一定要有resolve/reject,
    // 不然promise对象的状态一直会是pending,后续的then/catch都无法拿到数据,
    // promise对象最后会return自己(this),(链式)
    // 再通过then方法拿到resolve数据,catch方法拿到reject数据,
    // 下面两个参数resolve, reject不是变量,而是promise对象的方法
    // axios:
    // axios是基于promise封装的,axios就是promise对象,
    // axios.request(.get/.post)内部就把后端成功/失败响应数据给到resolve/reject,
    // 然后又return了this(链式),点出内部的then方法,then方法参数就是resolve方法给一个属性赋的值
    // await会直接拿到后面的promise对象的resolve里面的数据(不需要把resolve return出去)
    return new Promise((resolve, reject) => {
      const requestTime = new Date().getTime()
      // 接口调用代码执行流程:
      // 在外面的await调用接口,通过层层的return,最终await接口调用的是这个new promise对象,
      // (await会直接拿到后面的promise对象的resolve里面的数据),
      // 然后里面this._axios.request会立马执行,内部响应成功数据给到resolve,内部return了this,
      // 然后this._axios.request.then里上面一行的resolve的数据给到.then方法的参数,
      // .then方法里面,又把响应成功参数给到属于new promise对象的resolve里面,(不考虑失败情况)
      // 那么外面接口的await就会拿到这个new promise对象的resolve函数内部的数据
      this._axios
        .request<any, T>(config) // axios内部就写好了resolve/reject,把后端成功/失败响应数据给到resolve/reject
        .then((res) => {
          // 这个resolve是new promise对象的resolve
          resolve(res) //这里箭头函数不是return,是将axios响应成功数据给到new promise对象的resolve
        })
        .catch((err) => {
          reject(err)
          // 为什么这里需要return err呢
          // 是因为await只会有then的数据,为了用try、catch捕捉错误,所以需要return err错误信息
          return err
        })
        .finally(() => {
          const responseTime = new Date().getTime()
          console.log((responseTime - requestTime) / 1000)
        })
    })
  }
  // 这里封装get、post、put、delete方法
  // 封装get请求,用ts限制传入参数(请求地址、参数),返回promise对象里面包括后端数据类型
  get<T>(config: { url: string; params?: any }): Promise<T> {
    // 返回axios方法,里面写好了get方法
    return this.request<T>({ ...config, method: 'GET' })
  }
  post<T>(config: { url: string; data?: any }): Promise<T> {
    // 返回axios方法,里面写好了post方法
    return this.request<T>({ ...config, method: 'post' })
  }
  put<T>(config: { url: string; data?: any }): Promise<T> {
    // 返回axios方法,里面写好了put方法
    return this.request<T>({ ...config, method: 'put' })
  }
  delete<T>(config: { url: string; params?: any }): Promise<T> {
    // 返回axios方法,里面写好了delete方法
    return this.request<T>({ ...config, method: 'delete' })
  }
}

// 实例axios对象,并配置基地址和延迟时间
const request = new Request({
  // 基地址/开头就会自动转换为相对于浏览器地址
  baseURL: '/api',
  timeout: 5000
})

// 暴露实例好的axios实例
export default request
